/*
 * Javassist, a Java-bytecode translator toolkit.
 * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License.  Alternatively, the contents of this file may be used under
 * the terms of the GNU Lesser General Public License Version 2.1 or later,
 * or the Apache License Version 2.0.
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 */

package com.feilong.lib.javassist.runtime;

/**
 * A support class for implementing <code>$sig</code> and
 * <code>$type</code>.
 * This support class is required at runtime
 * only if <code>$sig</code> or <code>$type</code> is used.
 */
public class Desc{

    /**
     * Specifies how a <code>java.lang.Class</code> object is loaded.
     *
     * <p>
     * If true, it is loaded by:
     * 
     * <pre>
     * Thread.currentThread().getContextClassLoader().loadClass()
     * </pre>
     * <p>
     * If false, it is loaded by <code>Class.forName()</code>.
     * The default value is false.
     */
    public static boolean useContextClassLoader = false;

    private static Class<?> getClassObject(String name) throws ClassNotFoundException{
        if (useContextClassLoader){
            return Class.forName(name, true, Thread.currentThread().getContextClassLoader());
        }
        return Class.forName(name);
    }

    /**
     * Interprets the given class name.
     * It is used for implementing <code>$class</code>.
     */
    public static Class<?> getClazz(String name){
        try{
            return getClassObject(name);
        }catch (ClassNotFoundException e){
            throw new RuntimeException(
                            "$class: internal error, could not find class '" + name + "' (Desc.useContextClassLoader: "
                                            + Boolean.toString(useContextClassLoader) + ")",
                            e);
        }
    }

    /**
     * Interprets the given type descriptor representing a method
     * signature. It is used for implementing <code>$sig</code>.
     */
    public static Class<?>[] getParams(String desc){
        if (desc.charAt(0) != '('){
            throw new RuntimeException("$sig: internal error");
        }

        return getType(desc, desc.length(), 1, 0);
    }

    /**
     * Interprets the given type descriptor.
     * It is used for implementing <code>$type</code>.
     */
    public static Class<?> getType(String desc){
        Class<?>[] result = getType(desc, desc.length(), 0, 0);
        if (result == null || result.length != 1){
            throw new RuntimeException("$type: internal error");
        }

        return result[0];
    }

    private static Class<?>[] getType(String desc,int descLen,int start,int num){
        Class<?> clazz;
        if (start >= descLen){
            return new Class[num];
        }

        char c = desc.charAt(start);
        switch (c) {
            case 'Z':
                clazz = Boolean.TYPE;
                break;
            case 'C':
                clazz = Character.TYPE;
                break;
            case 'B':
                clazz = Byte.TYPE;
                break;
            case 'S':
                clazz = Short.TYPE;
                break;
            case 'I':
                clazz = Integer.TYPE;
                break;
            case 'J':
                clazz = Long.TYPE;
                break;
            case 'F':
                clazz = Float.TYPE;
                break;
            case 'D':
                clazz = Double.TYPE;
                break;
            case 'V':
                clazz = Void.TYPE;
                break;
            case 'L':
            case '[':
                return getClassType(desc, descLen, start, num);
            default:
                return new Class[num];
        }

        Class<?>[] result = getType(desc, descLen, start + 1, num + 1);
        result[num] = clazz;
        return result;
    }

    private static Class<?>[] getClassType(String desc,int descLen,int start,int num){
        int end = start;
        while (desc.charAt(end) == '['){
            ++end;
        }

        if (desc.charAt(end) == 'L'){
            end = desc.indexOf(';', end);
            if (end < 0){
                throw new IndexOutOfBoundsException("bad descriptor");
            }
        }

        String cname;
        if (desc.charAt(start) == 'L'){
            cname = desc.substring(start + 1, end);
        }else{
            cname = desc.substring(start, end + 1);
        }

        Class<?>[] result = getType(desc, descLen, end + 1, num + 1);
        try{
            result[num] = getClassObject(cname.replace('/', '.'));
        }catch (ClassNotFoundException e){
            // "new RuntimeException(e)" is not available in JDK 1.3.
            throw new RuntimeException(e.getMessage());
        }

        return result;
    }
}
